New StatusIconDispatcher dispatches icons to the system or message tray.
Based on a patch from Matt Novenstern
https://bugzilla.gnome.org/show_bug.cgi?id=608869
const TelepathyClient = imports.ui.telepathyClient;
const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier;
+const StatusIconDispatcher = imports.ui.statusIconDispatcher;
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
let modalActorFocusStack = [];
let uiGroup = null;
let magnifier = null;
+let statusIconDispatcher = null;
let _errorLogStack = [];
let _startDate;
overview = new Overview.Overview();
chrome = new Chrome.Chrome();
magnifier = new Magnifier.Magnifier();
+ statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
panel = new Panel.Panel();
wm = new WindowManager.WindowManager();
messageTray = new MessageTray.MessageTray();
this._notifications = {};
this._busProxy = new Bus();
+ Main.statusIconDispatcher.connect('message-icon-added', Lang.bind(this, this._onTrayIconAdded));
+ Main.statusIconDispatcher.connect('message-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
+
Shell.WindowTracker.get_default().connect('notify::focus-app',
Lang.bind(this, this._onFocusAppChanged));
Main.overview.connect('hidden',
}
},
+ _newSource: function(title, pid) {
+ let source = new Source(title, pid);
+ this._sources[pid] = source;
+
+ source.connect('clicked', Lang.bind(this,
+ function() {
+ source.destroy();
+ }));
+ source.connect('destroy', Lang.bind(this,
+ function() {
+ delete this._sources[pid];
+ }));
+
+ Main.messageTray.add(source);
+ return source;
+ },
+
Notify: function(appName, replacesId, icon, summary, body,
actions, hints, timeout) {
let id;
this._senderToPid[sender] = pid;
source = this._sources[pid];
- if (!source) {
- source = new Source(appName, pid);
- source.connect('clicked', Lang.bind(this,
- function() {
- source.destroy();
- }));
- source.connect('destroy', Lang.bind(this,
- function() {
- delete this._sources[pid];
- delete this._senderToPid[sender];
- }));
-
- this._sources[pid] = source;
- Main.messageTray.add(source);
- }
+ if (!source)
+ source = this._newSource(appName, pid);
+ source.connect('destroy', Lang.bind(this,
+ function() {
+ delete this._senderToPid[sender];
+ }));
this._notifyForSource(source, ndata);
}));
notification.setUrgent(hints.urgency == Urgency.CRITICAL);
- let sourceIconActor = source.app ? null : this._iconForNotificationData(icon, hints, source.ICON_SIZE);
+ let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
source.notify(notification, sourceIconActor);
},
'org.freedesktop.Notifications',
'ActionInvoked', 'us',
[id, action]);
+ },
+
+ _onTrayIconAdded: function(o, icon) {
+ let source = this._sources[icon.pid];
+ if (!source)
+ source = this._newSource(icon.title, icon.pid);
+ source.setTrayIcon(icon);
+ },
+
+ _onTrayIconRemoved: function(o, icon) {
+ let source = this._sources[icon.pid];
+ if (source)
+ source.destroy();
}
};
_init: function(title, pid) {
MessageTray.Source.prototype._init.call(this, title);
- this.app = Shell.WindowTracker.get_default().get_app_from_pid(pid);
- if (this.app) {
+ this._pid = pid;
+ this._setApp();
+ if (this.app)
this.title = this.app.get_name();
- this._setSummaryIcon(this.app.create_icon_texture(this.ICON_SIZE));
- }
+ else
+ this.useNotificationIcon = true;
},
notify: function(notification, icon) {
- if (icon)
+ if (!this.app)
+ this._setApp();
+ if (!this.app && icon)
this._setSummaryIcon(icon);
MessageTray.Source.prototype.notify.call(this, notification);
},
+ _setApp: function() {
+ this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid);
+ if (!this.app)
+ return;
+
+ // Only override the icon if we were previously using
+ // notification-based icons (ie, not a trayicon)
+ if (this.useNotificationIcon) {
+ this.useNotificationIcon = false;
+ this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
+ }
+ },
+
+ setTrayIcon: function(icon) {
+ this._setSummaryIcon(icon);
+ this.useNotificationIcon = false;
+ },
+
clicked: function() {
this.openApp();
MessageTray.Source.prototype.clicked.call(this);
const SPINNER_SPEED = 0.02;
const STANDARD_TRAY_ICON_ORDER = ['a11y', 'keyboard', 'volume', 'bluetooth', 'network', 'battery'];
-const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
- 'bluetooth-applet': 'bluetooth',
- 'gnome-volume-control-applet': 'volume',
- 'nm-applet': 'network',
- 'gnome-power-manager': 'battery'
-};
const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = {
'a11y': imports.ui.status.accessibility.ATIndicator
};
/* right */
- // Yet-another-Ubuntu-workaround - we have to kill their
- // app-indicators, so that applications fall back to normal
- // status icons
- // http://bugzilla.gnome.org/show_bug.cgi=id=621382
- let p = new Shell.Process({ args: ['pkill', '-f',
- '^([^ ]*/)?indicator-application-service$']});
- p.run();
-
// System status applets live in statusBox, while legacy tray icons
// live in trayBox
// The trayBox is hidden when there are no tray icons.
this._menus.addMenu(indicator.menu);
}
- this._traymanager = new Shell.TrayManager();
- this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded));
- this._traymanager.connect('tray-icon-removed',
- Lang.bind(this, function(o, icon) {
- trayBox.remove_actor(icon);
-
- if (trayBox.get_children().length == 0)
- trayBox.hide();
- this._recomputeTraySize();
- }));
- this._traymanager.manage_stage(global.stage);
+ Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
+ Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
this._statusmenu = new StatusMenu.StatusMenuButton();
this._menus.addMenu(this._statusmenu.menu);
});
},
- _onTrayIconAdded: function(o, icon) {
- let wmClass = icon.wm_class.toLowerCase();
-
+ _onTrayIconAdded: function(o, icon, role) {
icon.height = PANEL_ICON_SIZE;
- let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
- if (!role) {
- // Unknown icons go first in undefined order
- this._trayBox.insert_actor(icon, 0);
- } else {
- if (STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]) {
- // This icon is legacy, and replaced by a Shell version
- // Hide it
- return;
- }
- icon._role = role;
- // Figure out the index in our well-known order for this icon
- let position = STANDARD_TRAY_ICON_ORDER.indexOf(role);
- icon._rolePosition = position;
- let children = this._trayBox.get_children();
- let i;
- // Walk children backwards, until we find one that isn't
- // well-known, or one where we should follow
- for (i = children.length - 1; i >= 0; i--) {
- let rolePosition = children[i]._rolePosition;
- if (!rolePosition || position > rolePosition) {
- this._trayBox.insert_actor(icon, i + 1);
- break;
- }
- }
- if (i == -1) {
- // If we didn't find a position, we must be first
- this._trayBox.insert_actor(icon, 0);
+ if (STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION[role]) {
+ // This icon is legacy, and replaced by a Shell version
+ // Hide it
+ return;
+ }
+ // Figure out the index in our well-known order for this icon
+ let position = STANDARD_TRAY_ICON_ORDER.indexOf(role);
+ icon._rolePosition = position;
+ let children = this._trayBox.get_children();
+ let i;
+ // Walk children backwards, until we find one that isn't
+ // well-known, or one where we should follow
+ for (i = children.length - 1; i >= 0; i--) {
+ let rolePosition = children[i]._rolePosition;
+ if (!rolePosition || position > rolePosition) {
+ this._trayBox.insert_actor(icon, i + 1);
+ break;
}
}
+ if (i == -1) {
+ // If we didn't find a position, we must be first
+ this._trayBox.insert_actor(icon, 0);
+ }
// Make sure the trayBox is shown.
this._trayBox.show();
- this._recomputeTraySize();
},
- // By default, tray icons have a spacing of TRAY_SPACING. However this
- // starts to fail if we have too many as can sadly happen; just jump down
- // to a spacing of 8 if we're over 6.
- // http://bugzilla.gnome.org/show_bug.cgi?id=590495
- _recomputeTraySize: function () {
- if (this._trayBox.get_children().length > 6)
- this._trayBox.add_style_pseudo_class('compact');
- else
- this._trayBox.remove_style_pseudo_class('compact');
+ _onTrayIconRemoved: function(o, icon) {
+ this._trayBox.remove_actor(icon);
},
_addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) {
--- /dev/null
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Lang = imports.lang;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+
+const MessageTray = imports.ui.messageTray;
+const NotificationDaemon = imports.ui.notificationDaemon;
+
+const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
+ 'bluetooth-applet': 'bluetooth',
+ 'gnome-volume-control-applet': 'volume',
+ 'nm-applet': 'network',
+ 'gnome-power-manager': 'battery'
+};
+
+function StatusIconDispatcher() {
+ this._init();
+}
+
+StatusIconDispatcher.prototype = {
+ _init: function() {
+ this._traymanager = new Shell.TrayManager();
+ this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded));
+ this._traymanager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
+ this._traymanager.manage_stage(global.stage);
+
+ // Yet-another-Ubuntu-workaround - we have to kill their
+ // app-indicators, so that applications fall back to normal
+ // status icons
+ // http://bugzilla.gnome.org/show_bug.cgi=id=621382
+ let p = new Shell.Process({ args: ['pkill', '-f', '^([^ ]*/)?indicator-application-service$']});
+ p.run();
+ },
+
+ _onTrayIconAdded: function(o, icon) {
+ let wmClass = icon.wm_class.toLowerCase();
+ let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
+ if (role)
+ this.emit('status-icon-added', icon, role);
+ else
+ this.emit('message-icon-added', icon);
+ },
+
+ _onTrayIconRemoved: function(o, icon) {
+ let wmClass = icon.wm_class.toLowerCase();
+ let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
+ if (role)
+ this.emit('status-icon-removed', icon);
+ else
+ this.emit('message-icon-removed', icon);
+ }
+};
+Signals.addSignalMethods(StatusIconDispatcher.prototype);
}
#endif
+ /* When the pointer leaves the stage to enter a child of the stage
+ * (like a notification icon), we don't want to produce Clutter leave
+ * events. But Clutter treats all leave events identically, so we
+ * need hide the detail = NotifyInferior events from it.
+ *
+ * Since Clutter doesn't see any event at all, this does mean that
+ * it won't produce an enter event on a Clutter actor that surrounds
+ * the child (unless it gets a MotionNotify before the Enter event).
+ * Other weirdness is likely also possible.
+ */
+ if ((xev->xany.type == EnterNotify || xev->xany.type == LeaveNotify)
+ && xev->xcrossing.detail == NotifyInferior
+ && xev->xcrossing.window == clutter_x11_get_stage_window (CLUTTER_STAGE (clutter_stage_get_default ())))
+ return TRUE;
+
return clutter_x11_handle_event (xev) != CLUTTER_X11_FILTER_CONTINUE;
}